/*
 * Copyright 2012 Andrea Parodi
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package eban.storebyrules;


import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import eban.storebyrules.results.Result;
import eban.storebyrules.typemanagement.FieldTypeManager;
import lombok.extern.log4j.Log4j;

import java.util.Set;
@Log4j
public abstract class AbstractStorage implements Storage {
    protected final long threadId = Thread.currentThread().getId();

    protected AbstractStorage() {
        FieldTypeManager.Registry.initialize();
    }

    private final Set<PersistentCollection<?>> models = Sets.newHashSet();

    public ImmutableSet<PersistentCollection<?>> getModels() {
        checkSameThreadAsConstructionTime();
        return ImmutableSet.copyOf(models);
    }

    protected abstract void commitInternal() throws Exception;

    protected abstract void rollbackInternal() throws Exception;


    @Override
    public <TBean> PersistentCollection<TBean> addModel(Class<TBean> clazz) {
        checkSameThreadAsConstructionTime();
        PersistentCollection<TBean> model = new PersistentCollectionImpl<TBean>(this, clazz);
        models.add(model);
        return model;
    }

    void checkIsCurrentTransaction(AbstractTransaction that) {
        Preconditions.checkState(currentTransaction == that, "transaction disposed");
    }

    public void close() {
        checkSameThreadAsConstructionTime();
        dispose();
    }
    public abstract void dispose();


    void disposeCurrentTransaction() {
        currentTransaction = null;
    }

    @Override public Transaction beginTransaction() {
        checkSameThreadAsConstructionTime();
        checkDbExists();
        Preconditions.checkState(currentTransaction == null, "transaction pending");

        if (AbstractStorage.log.isDebugEnabled())
            AbstractStorage.log.debug("Starting transaction");


        try {
            currentTransaction = buildTransaction();
        } catch (Exception e) {
            throw new PersistenceException("Unable to start transaction",e);
        }

        if (AbstractStorage.log.isDebugEnabled())
            AbstractStorage.log.debug("Transaction started");

        return currentTransaction;
    }

    protected abstract AbstractTransaction buildTransaction()throws Exception;

    protected Transaction currentTransaction;

    void checkSameThreadAsConstructionTime() {
        Preconditions.checkState(Thread.currentThread().getId() == threadId);
    }

    @Override
    public Result getRecords(String sql) {

        checkSameThreadAsConstructionTime();
        checkDbExists();


        if (AbstractStorage.log.isDebugEnabled())
            AbstractStorage.log.debug("Executing sql " + sql);

        Result result = getResult(sql);

        if (AbstractStorage.log.isDebugEnabled())
            AbstractStorage.log.debug("Executing sql " + sql);


        return result;


    }
    protected void createTables() {
        checkSameThreadAsConstructionTime();

        try {
            for (PersistentCollection<?> model : getModels())
                executeInternal(model.createTableSql());

        } catch (Exception e) {
            throw new PersistenceDesignException("Unable to create database", e);
        }



    }
    protected abstract void executeInternal(String sql);

    protected abstract Result getResult(String sql);


    protected final void checkDbExists() {
        checkSameThreadAsConstructionTime();

        createConnectionToNewOrExistingStorage();
    }

    protected abstract void createConnectionToNewOrExistingStorage();


}
